use crate::chess::*;

pub type SituationId = usize;

const TAGS: [char; 10] = ['无', '错', '差', '拙', '弱', '优', '强', '妙', '好', '正'];

pub fn get_tag(score: u8) -> char {
    if score <= 9 {
        TAGS[score as usize]
    } else {
        ' '
    }
}

/// 棋局中的一步
#[derive(Clone, Debug)]
pub struct Step {
    /// 走子的位置
    pub from: Pos,

    /// 落子的位置
    pub to: Pos,

    /// 下一个局面
    pub next: SituationId,

    /// 评分，分值1到9，显示为对应的标签，默认为0表示未评分
    pub score: u8,
}

impl Step {
    pub fn new(from: Pos, to: Pos, next: SituationId) -> Self {
        Step {
            from,
            to,
            next,
            score: 0,
        }
    }

    pub fn tag(&self) -> char {
        get_tag(self.score)
    }
}

impl PartialEq for Step {
    /// 相同的走法导致相同的局面
    fn eq(&self, other: &Self) -> bool {
        self.from == other.from && self.to == other.to
    }
}

impl ToString for Step {
    fn to_string(&self) -> String {
        if self.score == 0 {
            format!("{}-{}-{}", self.from, self.to, self.next)
        } else {
            format!("{}-{}-{}-{}", self.from, self.to, self.next, self.score)
        }
    }
}

/// 局面
#[derive(Clone, PartialEq, Debug)]
pub struct Situation {
    // 编号
    pub id: SituationId,

    // 棋子布局
    pub pieces: [u8; 90],

    // 可能的走法
    pub steps: Vec<Step>,
}

impl Situation {
    pub fn encode(pieces: &[Option<(Color, Piece)>; 90]) -> [u8; 90] {
        let mut code = [b'_'; 90];
        for (index, piece) in pieces.iter().enumerate() {
            if let Some((color, piece)) = piece {
                code[index] = match color {
                    Color::Red => piece.to_char(),
                    Color::Black => piece.to_char().to_ascii_lowercase(),
                };
            }
        }
        code
    }

    pub fn decode(&self) -> Vec<(Pos, Color, Piece)> {
        let mut pieces = Vec::new();
        for i in 0..self.pieces.len() {
            let p = self.pieces[i];
            if p != b'_' {
                let (color, piece) = if p.is_ascii_uppercase() {
                    (Color::Red, Piece::from_char(p).unwrap())
                } else {
                    (
                        Color::Black,
                        Piece::from_char(p.to_ascii_uppercase()).unwrap(),
                    )
                };
                pieces.push((Pos(i as u8), color, piece));
            }
        }
        pieces
    }

    pub fn add_step(&mut self, from: Pos, to: Pos, next: SituationId) -> bool {
        let step = Step::new(from, to, next);
        if self.steps.contains(&step) {
            false
        } else {
            self.steps.push(step);
            true
        }
    }

    pub fn delete_step(&mut self, from: Pos, to: Pos) -> bool {
        let len = self.steps.len();
        self.steps.retain(|step| step.from != from || step.to != to);
        self.steps.len() < len
    }

    pub fn piece_color(&self, pos: Pos) -> Option<Color> {
        let piece = self.pieces[pos.index()];
        if piece != b'_' {
            if piece.is_ascii_uppercase() {
                Some(Color::Red)
            } else {
                Some(Color::Black)
            }
        } else {
            None
        }
    }

    pub fn next_steps(&self, turn: Color) -> Vec<Step> {
        self.steps
            .iter()
            .filter_map(|step| {
                if self.piece_color(step.from) == Some(turn) {
                    Some(step.clone())
                } else {
                    None
                }
            })
            .collect::<Vec<_>>()
    }

    pub fn get_step_mut(&mut self, from: Pos, to: Pos) -> Option<&mut Step> {
        if let Some(index) = self
            .steps
            .iter()
            .position(|step| step.from == from && step.to == to)
        {
            Some(&mut self.steps[index])
        } else {
            None
        }
    }

    pub fn set_score(&mut self, from: Pos, to: Pos, score: u8) {
        if let Some(step) = self.get_step_mut(from, to) {
            step.score = score;
        }
    }

    pub fn contains(&self, pattern: &[(Piece, Pos)]) -> bool {
        for (piece, pos) in pattern {
            if self.pieces[pos.index()].to_ascii_uppercase() != piece.to_char() {
                return false;
            }
        }
        return true;
    }

    pub fn import_step(&mut self, step: Step) -> bool {
        if let Some(own_step) = self.get_step_mut(step.from, step.to) {
            if own_step.score == 0 && step.score != 0 {
                own_step.score = step.score;
                true
            } else {
                false
            }
        } else {
            self.steps.push(step);
            true
        }
    }
}
